﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;

namespace Calculator.Models
{
    public static class ExpressionUtil
    {
        public static Expression OrderOfOperations(Expression e)
        {
            e = Clone(e);
            MulDivOverAddSub(e);
            return e;
        }

        private static Expression Clone(Expression e)
        {
            if (e.IsConstant) return e;
            Expression result = e.IsUnary ? new Expression(e.Op, null) : new Expression(e.Op, null, null);
            if (e.Left != null) result.Left = Clone(e.Left);
            if (e.Right != null) result.Right = Clone(e.Right);

            return result;

        }

        public static void MulDivOverAddSub(Expression e)
        {
            if (e.IsConstant) return;
            while (MustReorderExpression(e)) Swap(e, e.Left);
            if (e.Left != null) MulDivOverAddSub(e.Left);
            if (e.Right != null) MulDivOverAddSub(e.Right);
            if (MustReorderExpression(e)) Swap(e, e.Left);

        }

        private static int GetOpWeight(ExpressionOp op)
        {
            switch (op)
            {
                case ExpressionOp.Pow: return 5;

                case ExpressionOp.Multiply:
                case ExpressionOp.Divide: return 4;

                case ExpressionOp.Add:
                case ExpressionOp.Substract: return 3;

                default: return 255;
            }
        }

        private static bool MustReorderExpression(Expression e)
        {
            if (e.Right == null) return false;
            Expression left = e.Left;
            if (left == null) return false;
            int opWeight1 = GetOpWeight(e.Op);
            int opWeight2 = GetOpWeight(left.Op);
            return opWeight1 > opWeight2;
        }

        /// <summary>
        /// Swaps an expression to priorize multiplication over addition (or similar).
        /// </summary>
        /// <param name="a">expression with multiplication/division op.</param>
        /// <param name="b">expression with add/substract op which is also the left parent of a.</param>
        ///<remarks>
        /// converts the expression as followed:
        /// 
        ///                  (*)                              (+) 
        ///             /          \                      /          \
        ///          (+)          3                    1          (*)
        ///        /     \                                         /    \
        ///      1       2                                       2     3
        ///      
        /// where (*) = a and (+) = b.
        ///</remarks>
        private static void Swap(Expression a, Expression b)
        {
            ExpressionOp op = a.Op;
            a.Op = b.Op;
            b.Op = op;
            Expression r = a.Right;
            a.Right = b;
            a.Left = b.Left;
            b.Left = b.Right;
            b.Right = r;
        }
    }
}
